package com.chatplus.application.datasource.json;

import com.chatplus.application.common.util.Casts;
import com.chatplus.application.datasource.annotation.JsonCollectionGenericType;
import com.chatplus.application.datasource.json.support.JacksonJsonCollectionPostInterceptor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 抽象对象字段或属性的 json 集合类，用于实现在 orm 对象中，有引用范型集合的字段或属性时的特殊映射，
 * 如 Jackson Json，对于范型字段的映射返回一个 List Map 的，调用到字段或属性转型错误。
 * 查看{@link JacksonJsonCollectionPostInterceptor}
 *
 * @author maurice.chen
 */
public abstract class AbstractJsonCollectionPostInterceptor implements Interceptor {

    /**
     * mybatis 拦截器实现
     *
     * @param invocation 执行调用者
     * @return 返回对象
     * @throws Throwable 构造返回对象错误时抛出
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 通过默认的方法先执行得到结果
        Object result = invocation.proceed();
        // 获取当前接口的映射命令内容
        MappedStatement mappedStatement = Casts.cast(invocation.getArgs()[0]);

        // 构造类型内容对应的属性 map， 用于下面映射使用
        Map<Class<?>, List<PropertyDescriptor>> classPropertiesMap = new LinkedHashMap<>();
        for (ResultMap resultMap : mappedStatement.getResultMaps()) {
            // 通过类型获取对应带有 JsonCollectionGenericType 的属性
            List<PropertyDescriptor> propertyDescriptors = this.getJsonCollectionProperties(resultMap.getType());
            // 如果没有什么都不做
            if (propertyDescriptors.isEmpty()) {
                continue;
            }
            classPropertiesMap.put(resultMap.getType(), propertyDescriptors);
        }
        // 如果找不到任何泛型，直接返回结果集
        if (!Collection.class.isAssignableFrom(result.getClass()) || classPropertiesMap.isEmpty()) {
            return result;
        }
        // 否则映射泛型属性
        return mappingCollectionProperty(result, classPropertiesMap);
    }

    /**
     * 映射泛型属性
     *
     * @param result 数据内容
     * @param map    要映射的类 map
     * @return 新的数据内容
     */
    private Object mappingCollectionProperty(Object result, Map<Class<?>, List<PropertyDescriptor>> map) {
        // 转型成泛型对象
        Collection<?> collection = Casts.cast(result);

        Map<Class<?>, List<Object>> classMap = new LinkedHashMap<>();
        // 循环从泛型的值中去和 map 参数对比，如果 map 的key 存在泛型值得类型，将改值添加到 classMap 中，
        // 用于下面调用 doMappingResult 方法使用
        for (Object o : collection) {

            Optional<Class<?>> optional = map.keySet().stream().filter(t -> t.isAssignableFrom(o.getClass())).findFirst();
            if (optional.isEmpty()) {
                continue;
            }

            Class<?> key = optional.get();
            List<Object> list = classMap.computeIfAbsent(key, k -> new LinkedList<>());
            list.add(o);
        }

        List<Object> newResult = new LinkedList<>();
        // 循环构造好的对象值和类的映射，调用 doMappingResult 进行实际操作
        for (Map.Entry<Class<?>, List<Object>> entry : classMap.entrySet()) {
            List<Object> mapValues = entry
                .getValue()
                .stream()
                .map(v -> doMappingResult(v, entry.getKey(), map.get(entry.getKey())))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
            newResult.addAll(mapValues);
        }

        return newResult;
    }

    /**
     * 映射返回结果集
     *
     * @param result              从数据中查询出来的对象内容
     * @param type                mybatis 的 result type 类型
     * @param propertyDescriptors 改对象对应的属性集合
     * @return 新的数据对象
     */
    protected abstract Object doMappingResult(Object result, Class<?> type, List<PropertyDescriptor> propertyDescriptors);

    /**
     * 通过类型获取对应带有 JsonCollectionGenericType 的属性
     *
     * @param targetClass 目标类型
     * @return 属性集合
     */
    protected List<PropertyDescriptor> getJsonCollectionProperties(Class<?> targetClass) {
        PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(targetClass);
        return Arrays
            .stream(propertyDescriptors)
            .filter(p -> this.getJsonCollectionGenericType(p, targetClass) != null)
            .collect(Collectors.toList());
    }

    /**
     * 获取 JsonCollectionGenericType 注解
     *
     * @param propertyDescriptor 属性
     * @param targetClass        目标对象
     * @return JsonCollectionGenericType
     */
    protected JsonCollectionGenericType getJsonCollectionGenericType(PropertyDescriptor propertyDescriptor, Class<?> targetClass) {
        JsonCollectionGenericType result = null;
        Method method = propertyDescriptor.getReadMethod();
        if (Objects.nonNull(method)) {
            result = AnnotatedElementUtils.findMergedAnnotation(method, JsonCollectionGenericType.class);
        }

        if (result == null) {
            Field field = ReflectionUtils.findField(targetClass, propertyDescriptor.getName());
            if (Objects.nonNull(field)) {
                result = AnnotatedElementUtils.findMergedAnnotation(field, JsonCollectionGenericType.class);
            }
        }

        return result;
    }

}
